Limpieza de datos con tidyverse
Bienvenidos a este tutorial, en el que voy a hacer una introducción
al tidyverse y
como utilizarlo para hacer una de las tareas más tediosas: limpiar
datos. 1
tidyverseTidyverse es
una colección de paquetes diseñados para ayudar a los usuarios de datos
a trabajar de manera más eficiente con sus datos. La mayoría de las
funciones de tidyverse
tienen funciones equivalentes en base R (es decir, el
conjunto de funciones que se cargan automáticamente cuando se inicia
R). La diferencia es que las funciones de tidyverse
suelen ser más rápidas, la sintaxis puede ser, en general, más clara y
fácil de usar y, lo que es más importante, todas siguen una estructura
similar y los resultados son consistentes entre paquetes (dentro de los
límites, esto depende de la naturaleza de la función).
Nota técnica: tidyverse
utiliza principios de “evaluación no estándar” (NSE) en sus funciones.
NSE es un tema complicado, pero, en resumen, significa que los paquetes
en tidyverse
siguen reglas un poco diferentes a la función base R. Las funciones de
tidyverse
son más fáciles de usar, pero, si está acostumbrado a las funciones R
básicas, le costará un poco de esfuerzo reorientar su cerebro de
programación R. ¡Solo una advertencia amistosa antes de sumergirnos!
tidyverseComo dije anteriormente, tidyverse es
una colección de muchos paquetes. El core tidyverse
incluye los paquetes que probablemente usará en los análisis de datos
cotidianos. A partir detidyverse1.3.0,
los siguientes paquetes se incluyen en el core tidyverse:
| Paquete | Propósito |
|---|---|
| dplyr | para organizar datos |
| ggplot2 | para generar visualizaciones |
| tidyr | para reestructurar datos. |
| readr | para leer datos tabulares |
| purrr | transmisión avanzada de funciones a través de datos |
| tibble | para manejar datos en forma tabular |
| stringr | para manejar “strings” (variables de caracteres) |
| forcats | para manejar datos categóricos |
Para llamar el paquete podemos usar las funciones
library(), require(), o si usamos el paquete
rio, p_load(). Usemos por ahora la función
library()
library("tidyverse")## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.2 ──
## ✔ ggplot2 3.3.6 ✔ purrr 0.3.5
## ✔ tibble 3.1.8 ✔ dplyr 1.0.10
## ✔ tidyr 1.2.1 ✔ stringr 1.4.1
## ✔ readr 2.1.2 ✔ forcats 0.5.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
El mensaje indica los paquetes que fueron cargados, y los mensajes de advertencia indican que hay nombres de función idénticos en diferentes paquetes. El último paquete cargado anulará las funciones de los paquetes cargados anteriormente cuando surjan tales conflictos. Por lo tanto, el orden en que se cargan los paquetes puede ser importante. Sin embargo, se puede acceder a cualquier función agregando un prefijo del nombre del paquete y dos puntos a una función en particular.
En este tutorial, vamos a utilizar datos reales obtenidos del portal de datos abiertos de la ciudad de Nueva York y analizar si existe una correlación entre la composición demográfica de los High Schools de la ciudad y los resultados de las pruebas estandarizadas de Aptitud Académica (SAT) que toman la mayoría de los estudiantes de secundaria en los Estados Unidos
Para nuestro análisis vamos a utilizar dos bases de datos una que contiene la información demográfica y otra que contiene los resultados del SAT:
demog.csv: contiene datos en formato
.csv sobre la demografía de todos los estudiantes de la
ciudad de Nueva York para los años que van desde 2006 a 2012. Estos
datos incluyen información sobre la raza y el sexo, entre otros. Los
datos fueron descargados el 01/12/2023 del siguiente link
2012_SAT_Results.csv: contiene datos en formato
.csv sobre los puntajes de la Pruebas de Aptitud Académica
(SAT). Los datos contienen información de las calificaciones promedio en
cada una de las secciones de las pruebas: lectura crítica, matemáticas y
escritura. Los datos fueron descargados el 01/12/2023 del siguiente link
Para leer estos datos en formato .csv podemos utilizar
dos funciones: read.csv() que pertenece a
R base y read_csv() que pertenece al paquete
readr del universo tidyverse
sat <- read.csv("https://raw.githubusercontent.com/ignaciomsarmiento/datasets/main/2012_SAT_Results.csv")
demog<- read_csv("https://raw.githubusercontent.com/ignaciomsarmiento/datasets/main/demog.csv")¿En qué se diferencian las funciones read.csv() (de base
R) y read_csv (de readr)? La
respuesta tiene que ver con el tipo de objeto que importa cada función,
mientras que read_csv() importa datos en R como un
tibble, read.csv() importa un
data.frame de R base.
Esto podemos verlo usando la función str()
str(sat)## 'data.frame': 478 obs. of 6 variables:
## $ DBN : chr "01M292" "01M448" "01M450" "01M458" ...
## $ SCHOOL.NAME : chr "HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES" "UNIVERSITY NEIGHBORHOOD HIGH SCHOOL" "EAST SIDE COMMUNITY SCHOOL" "FORSYTH SATELLITE ACADEMY" ...
## $ Num.of.SAT.Test.Takers : chr "29" "91" "70" "7" ...
## $ SAT.Critical.Reading.Avg..Score: chr "355" "383" "377" "414" ...
## $ SAT.Math.Avg..Score : chr "404" "423" "402" "401" ...
## $ SAT.Writing.Avg..Score : chr "363" "366" "370" "359" ...
str(demog)## spec_tbl_df [10,075 × 38] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ DBN : chr [1:10075] "01M015" "01M015" "01M015" "01M015" ...
## $ Name : chr [1:10075] "P.S. 015 ROBERTO CLEMENTE" "P.S. 015 ROBERTO CLEMENTE" "P.S. 015 ROBERTO CLEMENTE" "P.S. 015 ROBERTO CLEMENTE" ...
## $ schoolyear : num [1:10075] 20052006 20062007 20072008 20082009 20092010 ...
## $ fl_percent : chr [1:10075] "89.4" "89.4" "89.4" "89.4" ...
## $ frl_percent : num [1:10075] NA NA NA NA 96.5 96.5 89.4 NA NA NA ...
## $ total_enrollment : num [1:10075] 281 243 261 252 208 203 189 402 312 338 ...
## $ prek : num [1:10075] 15 15 18 17 16 13 13 15 13 28 ...
## $ k : num [1:10075] 36 29 43 37 40 37 31 43 37 48 ...
## $ grade1 : num [1:10075] 40 39 39 44 28 35 35 55 45 46 ...
## $ grade2 : num [1:10075] 33 38 36 32 32 33 28 53 52 47 ...
## $ grade3 : num [1:10075] 38 34 38 34 30 30 25 68 47 53 ...
## $ grade4 : num [1:10075] 52 42 47 39 24 30 28 59 61 48 ...
## $ grade5 : num [1:10075] 29 46 40 49 38 25 29 64 57 68 ...
## $ grade6 : num [1:10075] 38 NA NA NA NA NA NA 45 NA NA ...
## $ grade7 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade8 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade9 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade10 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade11 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade12 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ ell_num : num [1:10075] 36 38 52 48 40 30 20 37 30 40 ...
## $ ell_percent : num [1:10075] 12.8 15.6 19.9 19 19.2 14.8 10.6 9.2 9.6 11.8 ...
## $ sped_num : num [1:10075] 57 55 60 62 46 46 40 93 72 75 ...
## $ sped_percent : num [1:10075] 20.3 22.6 23 24.6 22.1 22.7 21.2 23.1 23.1 22.2 ...
## $ ctt_num : num [1:10075] 25 19 20 21 14 21 23 7 13 12 ...
## $ selfcontained_num: num [1:10075] 9 15 14 17 14 9 7 37 22 19 ...
## $ asian_num : num [1:10075] 10 18 16 16 16 13 12 40 30 42 ...
## $ asian_per : num [1:10075] 3.6 7.4 6.1 6.3 7.7 6.4 6.3 10 9.6 12.4 ...
## $ black_num : num [1:10075] 74 68 77 75 67 75 63 103 70 72 ...
## $ black_per : num [1:10075] 26.3 28 29.5 29.8 32.2 36.9 33.3 25.6 22.4 21.3 ...
## $ hispanic_num : num [1:10075] 189 153 157 149 118 110 109 207 172 186 ...
## $ hispanic_per : num [1:10075] 67.3 63 60.2 59.1 56.7 54.2 57.7 51.5 55.1 55 ...
## $ white_num : num [1:10075] 5 4 7 7 6 4 4 39 19 22 ...
## $ white_per : num [1:10075] 1.8 1.6 2.7 2.8 2.9 2 2.1 9.7 6.1 6.5 ...
## $ male_num : num [1:10075] 158 140 143 149 124 113 97 214 157 162 ...
## $ male_per : num [1:10075] 56.2 57.6 54.8 59.1 59.6 55.7 51.3 53.2 50.3 47.9 ...
## $ female_num : num [1:10075] 123 103 118 103 84 90 92 188 155 176 ...
## $ female_per : num [1:10075] 43.8 42.4 45.2 40.9 40.4 44.3 48.7 46.8 49.7 52.1 ...
## - attr(*, "spec")=
## .. cols(
## .. DBN = col_character(),
## .. Name = col_character(),
## .. schoolyear = col_double(),
## .. fl_percent = col_character(),
## .. frl_percent = col_double(),
## .. total_enrollment = col_double(),
## .. prek = col_double(),
## .. k = col_double(),
## .. grade1 = col_double(),
## .. grade2 = col_double(),
## .. grade3 = col_double(),
## .. grade4 = col_double(),
## .. grade5 = col_double(),
## .. grade6 = col_double(),
## .. grade7 = col_double(),
## .. grade8 = col_double(),
## .. grade9 = col_double(),
## .. grade10 = col_double(),
## .. grade11 = col_double(),
## .. grade12 = col_double(),
## .. ell_num = col_double(),
## .. ell_percent = col_double(),
## .. sped_num = col_double(),
## .. sped_percent = col_double(),
## .. ctt_num = col_double(),
## .. selfcontained_num = col_double(),
## .. asian_num = col_double(),
## .. asian_per = col_double(),
## .. black_num = col_double(),
## .. black_per = col_double(),
## .. hispanic_num = col_double(),
## .. hispanic_per = col_double(),
## .. white_num = col_double(),
## .. white_per = col_double(),
## .. male_num = col_double(),
## .. male_per = col_double(),
## .. female_num = col_double(),
## .. female_per = col_double()
## .. )
## - attr(*, "problems")=<externalptr>
tibblesUn tibble es un tipo de objeto creado para tidyverse.
Cuando utilizamos readr para leer
datos los importa con este formato, y este formato es el que devuelven
muchas funciones de tidyverse.
El tibble es como data.frame de
R base con algunas otras reglas. Por ejemplo, tiene una
función de impresión simplificada que si uno accidentalmente escribe el
nombre del objeto: solo imprimirá las primeras 10 líneas y truncará el
número de columnas para que se muestren en su pantalla. Si escribe el
nombre del objeto de un data.frame tradicional, ¡se
imprimen las primeras 1000 líneas de datos!
Para crear un nuevo tibble, puede usar la función
tribble() para crear un tibble por filas, o la función
tibble() para construir un tibble en columnas, o
as.tibble() para convertir un objeto en un tibble.
Hay algunos aspectos de tibbles que son fundamentalmente
diferentes de un data.frame estándar. Tibbles no maneja
bien los nombres de las filas y muchas funciones de tidyverse
descartan esa información. Esto a menudo está bien: los nombres de fila
son computacionalmente costosos de establecer y mantener. Además, puede
ser difícil filtrar y ordenar los datos según los nombres de las filas.
Sin embargo, otros paquetes (que no pertenecen al tidyverse)
se basan en el atributo de nombres de fila, así que tenga en cuenta este
comportamiento cuando utilice distintos paquetes.
Los tibbles tienen también algunas ventajas sobre los
data.frame tradicionales:
Para poder analizar la correlación que existe entre la composición demográfica de las escuelas y los resultados en el SAT, necesitamos entonces “limpiar” nuestros datos.
Comenzamos primero seleccionando las variables que son de interés
para llevar a cabo nuestro análisis. Para seleccionar variables podemos
utilizar la función select() del paquete dplyr. Esta se usa
para la seleccionar columnas y tiene la estructura general:
select(data, variables_to_select).
Por ejemplo, en este análisis queremos retener variables que brindan información sobre porcentajes de estudiantes de diferentes razas e información en que grado están:
demog <- demog %>%
select(DBN, Name, schoolyear, frl_percent, total_enrollment, asian_per, black_per, hispanic_per, white_per,starts_with("grade"),selfcontained_num)
str(demog) ## tibble [10,075 × 22] (S3: tbl_df/tbl/data.frame)
## $ DBN : chr [1:10075] "01M015" "01M015" "01M015" "01M015" ...
## $ Name : chr [1:10075] "P.S. 015 ROBERTO CLEMENTE" "P.S. 015 ROBERTO CLEMENTE" "P.S. 015 ROBERTO CLEMENTE" "P.S. 015 ROBERTO CLEMENTE" ...
## $ schoolyear : num [1:10075] 20052006 20062007 20072008 20082009 20092010 ...
## $ frl_percent : num [1:10075] NA NA NA NA 96.5 96.5 89.4 NA NA NA ...
## $ total_enrollment : num [1:10075] 281 243 261 252 208 203 189 402 312 338 ...
## $ asian_per : num [1:10075] 3.6 7.4 6.1 6.3 7.7 6.4 6.3 10 9.6 12.4 ...
## $ black_per : num [1:10075] 26.3 28 29.5 29.8 32.2 36.9 33.3 25.6 22.4 21.3 ...
## $ hispanic_per : num [1:10075] 67.3 63 60.2 59.1 56.7 54.2 57.7 51.5 55.1 55 ...
## $ white_per : num [1:10075] 1.8 1.6 2.7 2.8 2.9 2 2.1 9.7 6.1 6.5 ...
## $ grade1 : num [1:10075] 40 39 39 44 28 35 35 55 45 46 ...
## $ grade2 : num [1:10075] 33 38 36 32 32 33 28 53 52 47 ...
## $ grade3 : num [1:10075] 38 34 38 34 30 30 25 68 47 53 ...
## $ grade4 : num [1:10075] 52 42 47 39 24 30 28 59 61 48 ...
## $ grade5 : num [1:10075] 29 46 40 49 38 25 29 64 57 68 ...
## $ grade6 : num [1:10075] 38 NA NA NA NA NA NA 45 NA NA ...
## $ grade7 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade8 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade9 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade10 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade11 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade12 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ selfcontained_num: num [1:10075] 9 15 14 17 14 9 7 37 22 19 ...
El símbolo %>% es lo que se conoce como un operador
de pipa (pipe operator) y viene como parte de tidyverse.
Este nos permite concatenar comandos y funciones, diciendo básicamente
que al elemento anterior se le realice la operación siguiente. En
nuestro caso le estamos diciendo a R que del objeto
demog seleccione ciertas variables. No es necesario usar
este operador pero simplifica y mejora la legibilidad del código.
Podríamos haber hecho la misma operación sin este operador y este
luciría así:
demog <- select(demog, DBN, Name, schoolyear, frl_percent, total_enrollment, asian_per, black_per, hispanic_per, white_per,starts_with("grade"),selfcontained_num)select() contiene además una función muy útil que
selecciona variables que comienzan (starts_with()) o
finalizan (ends_with()) con cierto patrón.
Por error en el paso anterior incluí la variable
selfcontained_num que identifica el **Número total de
estudiantes en clases especiales* que no sirve para el análisis, puedo
removerla con la misma función más un signo menos (-) antes
de la variable que quiero eliminar:
demog <- demog %>%
select (-selfcontained_num)
str(demog) ## tibble [10,075 × 21] (S3: tbl_df/tbl/data.frame)
## $ DBN : chr [1:10075] "01M015" "01M015" "01M015" "01M015" ...
## $ Name : chr [1:10075] "P.S. 015 ROBERTO CLEMENTE" "P.S. 015 ROBERTO CLEMENTE" "P.S. 015 ROBERTO CLEMENTE" "P.S. 015 ROBERTO CLEMENTE" ...
## $ schoolyear : num [1:10075] 20052006 20062007 20072008 20082009 20092010 ...
## $ frl_percent : num [1:10075] NA NA NA NA 96.5 96.5 89.4 NA NA NA ...
## $ total_enrollment: num [1:10075] 281 243 261 252 208 203 189 402 312 338 ...
## $ asian_per : num [1:10075] 3.6 7.4 6.1 6.3 7.7 6.4 6.3 10 9.6 12.4 ...
## $ black_per : num [1:10075] 26.3 28 29.5 29.8 32.2 36.9 33.3 25.6 22.4 21.3 ...
## $ hispanic_per : num [1:10075] 67.3 63 60.2 59.1 56.7 54.2 57.7 51.5 55.1 55 ...
## $ white_per : num [1:10075] 1.8 1.6 2.7 2.8 2.9 2 2.1 9.7 6.1 6.5 ...
## $ grade1 : num [1:10075] 40 39 39 44 28 35 35 55 45 46 ...
## $ grade2 : num [1:10075] 33 38 36 32 32 33 28 53 52 47 ...
## $ grade3 : num [1:10075] 38 34 38 34 30 30 25 68 47 53 ...
## $ grade4 : num [1:10075] 52 42 47 39 24 30 28 59 61 48 ...
## $ grade5 : num [1:10075] 29 46 40 49 38 25 29 64 57 68 ...
## $ grade6 : num [1:10075] 38 NA NA NA NA NA NA 45 NA NA ...
## $ grade7 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade8 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade9 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade10 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade11 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
## $ grade12 : num [1:10075] NA NA NA NA NA NA NA NA NA NA ...
Para ordenar los datos podemos utilizar la función
arrange() que ordena de mayor a menor por default:
demog <- demog %>% arrange(total_enrollment)
head(demog)## # A tibble: 6 × 21
## DBN Name schoo…¹ frl_p…² total…³ asian…⁴ black…⁵ hispa…⁶ white…⁷ grade1
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 02M445 SEWARD … 2.01e7 NA 1 0 100 0 0 NA
## 2 12X191 IS 191 2.01e7 NA 1 0 100 0 0 NA
## 3 12X234 PS 234 … 2.01e7 NA 1 0 0 100 0 NA
## 4 17K479 ERASMUS… 2.01e7 NA 1 0 100 0 0 NA
## 5 27Q180 MS 180 … 2.01e7 NA 1 0 0 0 100 NA
## 6 06M090 I S 090… 2.01e7 NA 2 0 0 100 0 NA
## # … with 11 more variables: grade2 <dbl>, grade3 <dbl>, grade4 <dbl>,
## # grade5 <dbl>, grade6 <dbl>, grade7 <dbl>, grade8 <dbl>, grade9 <dbl>,
## # grade10 <dbl>, grade11 <dbl>, grade12 <dbl>, and abbreviated variable names
## # ¹schoolyear, ²frl_percent, ³total_enrollment, ⁴asian_per, ⁵black_per,
## # ⁶hispanic_per, ⁷white_per
o podemos hacerlo de manera descendente con la función
desc():
demog <- demog %>% arrange(desc(total_enrollment), desc(white_per))
head(demog)## # A tibble: 6 × 21
## DBN Name schoo…¹ frl_p…² total…³ asian…⁴ black…⁵ hispa…⁶ white…⁷ grade1
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 13K430 BROOKLY… 2.01e7 50.7 5332 60.3 10.2 7.9 21.3 NA
## 2 13K430 BROOKLY… 2.01e7 64.3 5140 60.4 10.7 7.7 21 NA
## 3 13K430 BROOKLY… 2.01e7 53.8 4947 58.8 12 8.3 20.5 NA
## 4 13K430 BROOKLY… 2.01e7 NA 4662 57.4 13.1 8 21 NA
## 5 20K490 FORT HA… 2.01e7 NA 4538 28.2 5.2 32.3 31.8 NA
## 6 10X440 DEWITT … 2.01e7 NA 4533 5.7 26 63.5 3.4 NA
## # … with 11 more variables: grade2 <dbl>, grade3 <dbl>, grade4 <dbl>,
## # grade5 <dbl>, grade6 <dbl>, grade7 <dbl>, grade8 <dbl>, grade9 <dbl>,
## # grade10 <dbl>, grade11 <dbl>, grade12 <dbl>, and abbreviated variable names
## # ¹schoolyear, ²frl_percent, ³total_enrollment, ⁴asian_per, ⁵black_per,
## # ⁶hispanic_per, ⁷white_per
A menudo también es importante seleccionar filas, una forma es
elegirlas basado en su orden. Para ello la función slice()
es útil ya que nos permite seleccionar un número específico. Por
ejemplo, si queremos seleccionar las primeras 5 filas:
demog_5 <- demog %>% slice(1:5)
head(demog_5)## # A tibble: 5 × 21
## DBN Name schoo…¹ frl_p…² total…³ asian…⁴ black…⁵ hispa…⁶ white…⁷ grade1
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 13K430 BROOKLY… 2.01e7 50.7 5332 60.3 10.2 7.9 21.3 NA
## 2 13K430 BROOKLY… 2.01e7 64.3 5140 60.4 10.7 7.7 21 NA
## 3 13K430 BROOKLY… 2.01e7 53.8 4947 58.8 12 8.3 20.5 NA
## 4 13K430 BROOKLY… 2.01e7 NA 4662 57.4 13.1 8 21 NA
## 5 20K490 FORT HA… 2.01e7 NA 4538 28.2 5.2 32.3 31.8 NA
## # … with 11 more variables: grade2 <dbl>, grade3 <dbl>, grade4 <dbl>,
## # grade5 <dbl>, grade6 <dbl>, grade7 <dbl>, grade8 <dbl>, grade9 <dbl>,
## # grade10 <dbl>, grade11 <dbl>, grade12 <dbl>, and abbreviated variable names
## # ¹schoolyear, ²frl_percent, ³total_enrollment, ⁴asian_per, ⁵black_per,
## # ⁶hispanic_per, ⁷white_per
Para nuestro análisis necesitamos la información de los High Schools.
Sin embargo, la base con los datos demográficos incluye información de
todos los colegios de la ciudad, independientemente si son High Schools
o no. Para filtrar solo los High Schools vamos a usar la información de
que las insituciones con individuos en los grados 9 a 12 son
instituciones secundarias. Por lo tanto, si hay información faltante en
esta variable, es porque no son High Schools. Esto lo podemos hacer
usando la función filter() que sigue la estructura:
filter(data, condition_for_filter):
demog <- demog %>% filter(grade9 != "NA")Es decir, nos quedamos con observaciones donde que no tienen valores
faltantes en la variable de interés: grade9. Podemos
también filtrar usando varias condiciones, por ejemplo:
demog <- demog %>% filter(grade9 != "NA", schoolyear == "20112012")que agrega la condición de que los datos pertenezcan al año escolar
2011-2012. Esto será importante porque vamos a unir esta base con los
resultados del SAT en 2012. Podríamos también filtrar utilizando el
criterio de que coincidan con múltiples valores. El operador
%in% es extremadamente valioso en estos casos:
demog_sub <- demog %>% filter(Name %in% c("WILLIAMSBURG PREPARATORY SCHOOL","THE SCHOOL FOR HUMAN RIGHTS","THE HIGH SCHOOL FOR GLOBAL CITIZENSHIP"))
table(demog_sub$Name)##
## THE HIGH SCHOOL FOR GLOBAL CITIZENSHIP THE SCHOOL FOR HUMAN RIGHTS
## 1 1
## WILLIAMSBURG PREPARATORY SCHOOL
## 1
Con las variables y las observaciones de interés seleccionadas y filtradas podemos comenzar a hacer operaciones sobre las columnas.
Por ejemplo, podemos querer cambiar el nombre de un variable. La
función rename() cambia el nombre de las variables usando
la siguiente notación:
rename(dataframe, newname = "oldname"):
demog <- demog %>% rename(AnoEscolar= schoolyear)En este caso cambiamos el nombre de la variable
schoolyear a AnoEscolar.
Podemos también querer crear una nueva variable. Aquí utilizamos la
función mutate(). Esta función sirve para crear (mutar)
nuevas variables, y tiene la sintaxis
mutate(data, new_variable_name = data_transformation):
Supongamos que queremos verificar que los porcentajes de la composición racial suman 100:
demog <- demog %>% mutate(perc_total= asian_per+black_per+hispanic_per+white_per)y con la función summary() de R base
podemos ver las estadísticas descriptivas de esta nueva variable:
summary(demog$perc_total)## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 88.40 99.00 99.50 99.16 99.80 100.10
Ocasionalmente, tendremos datos que necesitan pasar de formato
horizontal (wide) a uno vertical (long) o
viceversa. Esto es a veces necesario para graficar, para comparar puntos
de datos particulares, o porque otro paquete de R lo
requiere. Tidyr
contiene varias funciones que nos permiten hacer esto sin esfuerzo. Las
dos funciones claves son pivot_wider() para pasar de
formato long a wide y
pivot_longer() de wide a
long.
wide_to_long
Ilustremos esto en nuestro ejemplo, pero también aprovecharemos para
mostrar el poder de concatenar funciones del operador de pipa o pipe
operator. Vamos a transformar nuestros datos que está en formato
horizontal o wide respecto a la composición racial y lo
pasaremos al formato vertical o long:
demog_long <- demog %>%
select(DBN, AnoEscolar,Name,asian_per,black_per,hispanic_per,white_per) %>%
pivot_longer(cols = c(asian_per,black_per,hispanic_per,white_per),
names_to = "Race",
values_to = "Perc",
values_drop_na = T)
head(demog_long)## # A tibble: 6 × 5
## DBN AnoEscolar Name Race Perc
## <chr> <dbl> <chr> <chr> <dbl>
## 1 13K430 20112012 BROOKLYN TECHNICAL HIGH SCHOOL asian_per 60.3
## 2 13K430 20112012 BROOKLYN TECHNICAL HIGH SCHOOL black_per 10.2
## 3 13K430 20112012 BROOKLYN TECHNICAL HIGH SCHOOL hispanic_per 7.9
## 4 13K430 20112012 BROOKLYN TECHNICAL HIGH SCHOOL white_per 21.3
## 5 20K490 20112012 FORT HAMILTON HIGH SCHOOL asian_per 30.3
## 6 20K490 20112012 FORT HAMILTON HIGH SCHOOL black_per 5
Concatenamos entonces dos funciones, select() y
pivot_longer(), creando un nuevo objeto con los datos
demográficos para cada escuela en formato long. Al hacerlo
enviamos el nombre de las variables a una nueva variable llamada
Race y los valores de estas variables a una nueva que le
llamamos Perc.
Otra tarea que solemos realizar mucho es resumir información por
grupos. Supongamos que queremos colapsar (resumir) la base anterior
(demog_long) en el porcentaje total de las composiciones
raciales.
La función group_by() proporciona una herramienta útil
para dividir los datos en grupos según un factor común y la función
summarize() para aplicar una función de resumen a esos
datos. Colapsemos entonces por escuela y año escolar la suma de los
porcentajes de la composición racial de cada una de ellas:
demog_long_sum <- demog_long %>%
group_by(DBN, AnoEscolar,Name) %>%
summarize(perc_total=sum(Perc))Si hacemos el resumen podemos ver que coincide con la variable que creamos anteriormente que era la suma horizontal de estas composiciones:
summary(demog_long_sum$perc_total)## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 88.40 99.00 99.50 99.16 99.80 100.10
summary(demog$perc_total)## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 88.40 99.00 99.50 99.16 99.80 100.10
Poder unir distintas bases de datos utilizando una variable en común es una de las tareas más comunes en la limpieza de datos. Para poder unir dos bases de datos, es importante tener una variable en común que pueda ser automáticamente detectada o que se tenga que definir explícitamente.
La forma general de las función que sirven para unir bases en tidyverse es
type_join(x_data, y_data, ID_key_variable). Cuando unimos
los datos la(s) columna(s) usada(s) como identificadores
(ID_key_variable) solo se incluyen una vez en los datos
fusionados finales, es decir, la(s) columna(s) de clave duplicada(s) se
descarta(n). Sin embargo, si hay otras columnas (sin ID) con nombres
duplicados en los datos, las columnas duplicadas del primer conjunto de
datos (x_data) reciben un sufijo .x y las
columnas duplicadas del segundo conjunto de datos (y_data)
reciben un sufijo .y.
Dependiendo de la función que se utilice la base resultante contara
con todas las columnas de ambas bases o un subconjunto de ellas. Los
seis tipos incluidos en tidyverse
son:
El full_join(): es la opción más segura para evitar
la eliminación de datos, devuelve todo.
El inner_join(): sólo mantiene lo que es común entre
los conjuntos de datos.
El left_join(): une todas las filas de las
observaciones que se encuentran en el tibble declarado a la
izquierda o en primer lugar.
El right_join(): une todas las filas de las
observaciones que se encuentran en el tibble declarado a la
derecha o en segundo lugar.
El semi_join(): es como inner_join(),
pero sólo se conservan las filas con claves en ambos conjuntos de datos,
excepto que no conserva ningún dato del conjunto de datos que aparece a
la derecha o en segundo lugar.
El anti_join(): conserva las observaciones del
primer data.frame que no coinciden con el segundo
.
El último paso que nos queda antes de poder hacer nuestro análisis de correlación es unir las dos bases de datos.
La base del SAT tiene seis variables que, en conjunto, identifican a cada escuela y brindan información sobre los puntajes promedio que obtuvieron los estudiantes en cada escuela secundaria en las tres secciones del SAT: lectura crítica, matemáticas y escritura. Todas las variables son relevantes para nuestro propósito de comprender el efecto de la demografía en el rendimiento de la prueba y deben conservarse.
La variable DBN, es un identificador de las escuelas y
es común a ambas bases, la utilizaremos entonces como nuestra
ID_key_variable:
sat_demog_dir <- sat %>%
left_join(demog, by = c("DBN"))
str(sat_demog_dir)## 'data.frame': 478 obs. of 27 variables:
## $ DBN : chr "01M292" "01M448" "01M450" "01M458" ...
## $ SCHOOL.NAME : chr "HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES" "UNIVERSITY NEIGHBORHOOD HIGH SCHOOL" "EAST SIDE COMMUNITY SCHOOL" "FORSYTH SATELLITE ACADEMY" ...
## $ Num.of.SAT.Test.Takers : chr "29" "91" "70" "7" ...
## $ SAT.Critical.Reading.Avg..Score: chr "355" "383" "377" "414" ...
## $ SAT.Math.Avg..Score : chr "404" "423" "402" "401" ...
## $ SAT.Writing.Avg..Score : chr "363" "366" "370" "359" ...
## $ Name : chr "HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES" "UNIVERSITY NEIGHBORHOOD HIGH SCHOOL" "EAST SIDE COMMUNITY HIGH SCHOOL" "SATELLITE ACADEMY HS @ FORSYTHE STREET" ...
## $ AnoEscolar : num 20112012 20112012 20112012 20112012 20112012 ...
## $ frl_percent : num 88.6 71.8 71.8 72.8 80.7 NA 23 69.8 18 66.9 ...
## $ total_enrollment : num 422 394 598 224 367 ...
## $ asian_per : num 14 29.2 9.7 2.2 9.3 NA 27.8 0.5 15.1 1.7 ...
## $ black_per : num 29.1 22.6 23.9 34.4 31.6 NA 11.7 45.4 15.1 32.2 ...
## $ hispanic_per : num 53.8 45.9 55.4 59.4 56.9 NA 14.2 49.5 18.2 59.2 ...
## $ white_per : num 1.7 2.3 10.4 3.6 1.6 NA 44.9 4.1 49.8 6.3 ...
## $ grade1 : num NA NA NA NA NA NA 107 NA NA NA ...
## $ grade2 : num NA NA NA NA NA NA 139 NA NA NA ...
## $ grade3 : num NA NA NA NA NA NA 110 NA NA NA ...
## $ grade4 : num NA NA NA NA NA NA 114 NA NA NA ...
## $ grade5 : num NA NA NA NA NA NA 107 NA NA NA ...
## $ grade6 : num 32 NA 92 NA NA NA 149 NA NA NA ...
## $ grade7 : num 33 NA 73 NA NA NA 126 NA NA NA ...
## $ grade8 : num 50 NA 76 NA NA NA 117 NA NA NA ...
## $ grade9 : num 98 109 101 131 143 NA 117 5 184 50 ...
## $ grade10 : num 79 97 93 49 100 NA 123 89 162 63 ...
## $ grade11 : num 80 93 77 44 51 NA 147 59 128 38 ...
## $ grade12 : num 50 95 86 NA 73 NA 157 65 143 23 ...
## $ perc_total : num 98.6 100 99.4 99.6 99.4 NA 98.6 99.5 98.2 99.4 ...
Creamos entonces un nuevo objeto sat_demog_dir con la
función left_join que resulta de unir los colegios que
aparecen en la base del SAT, con los datos demográficos que tienen en
común el identificador del colegio DBN.
Con las bases unidas podemos ver como lucen las correlaciones entre
la composición demográfica y los resultados en el SAT de matemáticas.
Para ello utilizamos la función plot() de
R base que nos permite ver los diagramas de dispersión.
En primer lugar podemos ver los resultados en matemáticas y el porcentaje de Afrodescendientes en los colegios.
plot(sat_demog_dir$SAT.Math.Avg..Score,sat_demog_dir$black_per)Podemos hacer lo mismo con el porcentaje de blancos:
plot(sat_demog_dir$SAT.Math.Avg..Score,sat_demog_dir$white_per)o de asiáticos:
plot(sat_demog_dir$SAT.Math.Avg..Score,sat_demog_dir$asian_per)En este vídeo tutorial hicimos una demostración de cómo podemos limpiar datos con la librería tidyverse y un análisis de correlación sencillo. Un recurso útil para profundizar los conocimientos es el libro R for Data Science. También profundizaremos sobre estos temas en las clases sincrónica complementarias.
Otros recursos útiles son:
Aplican los “disclaimers” usuales. Si tenes comentarios,
sugerencias, no dudes en enviarme un mensaje por Slack,
serán muy bienvenidos y tenidos en cuenta para la calificación final.↩︎